package me.itsoo.artemis.framework.repository.mapper;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import me.itsoo.artemis.framework.core.util.ArrayUtils;
import me.itsoo.artemis.framework.core.util.Assert;
import me.itsoo.artemis.framework.core.util.CollectionUtils;
import me.itsoo.artemis.framework.repository.config.Configuration;
import me.itsoo.artemis.framework.repository.exception.TooManyResultsException;
import me.itsoo.artemis.framework.repository.executor.BoundSql;
import me.itsoo.artemis.framework.repository.executor.ExecuteType;
import me.itsoo.artemis.framework.repository.executor.SqlExecutor;
import me.itsoo.artemis.framework.repository.mapper.condition.AbstractCondition;
import me.itsoo.artemis.framework.repository.mapper.condition.ConditionSimpleFactory;
import me.itsoo.artemis.framework.repository.mapper.condition.QueryCondition;
import me.itsoo.artemis.framework.repository.mapper.resource.AbstractResource;
import me.itsoo.artemis.framework.repository.mapper.resource.CreateResource;
import me.itsoo.artemis.framework.repository.mapper.resource.DeleteResource;
import me.itsoo.artemis.framework.repository.mapper.resource.QueryResource;
import me.itsoo.artemis.framework.repository.mapper.resource.ResourceSimpleFactory;
import me.itsoo.artemis.framework.repository.mapper.resource.UpdateResource;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * AbstractSqlMapper
 *
 * @param <T> T
 * @author zxy
 * @version 0.0.1
 * @since 2023/5/11
 */
@Slf4j
public abstract class AbstractSqlMapper<T> implements SqlMapper<T>, MapperBoundSqlFactory {

    private final Class<T> type;

    @Getter
    private final MapperDefinition md;

    @Getter
    private Configuration config;

    @Getter
    private SqlExecutor sqlExecutor;

    @Setter
    protected ResourceSimpleFactory resourceFactory = new ResourceSimpleFactory();

    @Setter
    protected ConditionSimpleFactory conditionFactory = new ConditionSimpleFactory();

    protected AbstractSqlMapper(Class<T> type) {
        this.type = type;
        this.md = MapperDefinition.create(type);
    }

    protected AbstractSqlMapper(Class<T> type, Configuration config) {
        this(type);

        setConfig(config);
    }

    @Override
    public void setConfig(Configuration config) {
        this.config = config;
        this.sqlExecutor = config.getSqlExecutor();
    }

    @Override
    public T selectOne(Object pk) {
        final List<T> coll = select(Collections.singletonList(pk));
        final int total = coll.size();
        if (total > 1) {
            throw new TooManyResultsException(total);
        }

        return CollectionUtils.findFirst(coll);
    }

    @Override
    public List<T> select(Collection<?> pkColl) {
        final QueryCondition cond = conditionFactory.createQueryCondition(md, pkColl);
        return select(cond);
    }

    @Override
    public List<T> select(AbstractCondition cond) {
        final QueryResource res = resourceFactory.createQueryResource(md);
        Assert.notNull(res, "'queryResource' cannot be null");
        return select(res, cond);
    }

    @Override
    public List<T> select(AbstractResource res, AbstractCondition cond) {
        final BoundSql boundSql = getBoundSql(res, cond);
        return sqlExecutor.executeSelect(md, boundSql, type);
    }

    @Override
    public int insert(T dataObject) {
        if (Objects.isNull(dataObject)) {
            return 0;
        }
        Assert.isTypeOf(dataObject, type, "'dataObject' type is not " + type.getCanonicalName());

        final CreateResource res = resourceFactory.createCreateResource(md, dataObject);
        Assert.notNull(res, "'createResource' cannot be null");

        final BoundSql boundSql = getBoundSql(res, null);
        return sqlExecutor.executeUpdate(md, boundSql, ExecuteType.INSERT);
    }

    @Override
    public int insertSelective(T dataObject) {
        Assert.isTypeOf(dataObject, type, "'dataObject' type is not " + type.getCanonicalName());
        // TODO:
        return 0;
    }

    @Override
    public int update(T dataObject) {
        if (Objects.isNull(dataObject)) {
            return 0;
        }
        Assert.isTypeOf(dataObject, type, "'dataObject' type is not " + type.getCanonicalName());

        final UpdateResource res = resourceFactory.createUpdateResource(md, dataObject);
        Assert.notNull(res, "'updateResource' cannot be null");

        final QueryCondition cond = conditionFactory.createQueryCondition(md, dataObject);
        // 拦截全表更新操作
        Assert.notNull(cond, "Prohibition of table update operation");

        final BoundSql boundSql = getBoundSql(res, cond);
        return sqlExecutor.executeUpdate(md, boundSql, ExecuteType.UPDATE);
    }

    @Override
    public int updateSelective(T dataObject) {
        if (Objects.isNull(dataObject)) {
            return 0;
        }

        Assert.isTypeOf(dataObject, type, "'dataObject' type is not " + type.getCanonicalName());
        // TODO:
        return 0;
    }

    @Override
    public int delete(Collection<?> pkColl) {
        final DeleteResource res = resourceFactory.createDeleteResource(md);
        Assert.notNull(res, "'deleteResource' cannot be null");

        final QueryCondition cond = conditionFactory.createQueryCondition(md, pkColl);
        // 拦截全表删除
        Assert.notNull(cond, "Prohibition of full table deletion");

        final BoundSql boundSql = getBoundSql(res, cond);
        return sqlExecutor.executeUpdate(md, boundSql, ExecuteType.DELETE);
    }

    @Override
    public int delete(Object pk) {
        return delete(Collections.singletonList(pk));
    }

    @Override
    public BoundSql getBoundSql(AbstractResource res, AbstractCondition cond) {
        String sql = "";
        Object[] args = new Object[0];

        if (Objects.nonNull(res)) {
            sql = sql + res;
            args = ArrayUtils.merge(args, res.getResourceArgs());
        }
        if (Objects.nonNull(cond)) {
            sql = sql + " " + cond;
            args = ArrayUtils.merge(args, cond.getConditionArgs());
        }

        return BoundSql.create(sql, args);
    }
}
